﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using DependencyAnalysis.Internal;
using LiskovSubstitutionLibrary;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.Principles
{
    [TestClass]
    public class MethodenreinheitTest
    {
        private static readonly IDependencyModel Model;
        private TypeDescriptor _purity1;
        private TypeDescriptor _purity2;
        private TypeDescriptor _purity3;

        static MethodenreinheitTest()
        {
            var setup = Setup.BuildStandardSetup();
            Model = setup.Model;
            setup.StartAnalysis(new[] { typeof(BaseClass).Assembly });
            var methodenreinheit = new Methodenreinheit(Model);
            methodenreinheit.Evaluate();
        }

        [TestInitialize]
        public void Initialize()
        {
            _purity1 = DescriptorBuilder.BuildTypeDescriptor(typeof(Purity1));
            _purity2 = DescriptorBuilder.BuildTypeDescriptor(typeof(Purity2));
            _purity3 = DescriptorBuilder.BuildTypeDescriptor(typeof(Purity3));
        }

        [TestMethod]
        public void WhenSettingsStaticFieldThenMarkModifyCommon()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "ModifyStaticField");
            Assert.IsTrue((method.Purity & Purity.ModifyCommon) == Purity.ModifyCommon);
        }

        [TestMethod]
        public void WhenSettingStateThenMarkPure()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "PureMethod");
            Assert.AreEqual(Purity.Pure, method.Purity);
        }

        [TestMethod]
        public void WhenSettingsOwnMemberThenMarkModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "ModifyOwnMember");
            Assert.IsTrue((method.Purity & Purity.ModifySelf) == Purity.ModifySelf);
        }

        [TestMethod]
        public void WhenSettingsBaseMemberThenMarkModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "ModifyBaseMember");
            Assert.IsTrue((method.Purity & Purity.ModifySelf) == Purity.ModifySelf);
        }

        [TestMethod]
        public void WhenCallModifyingMethodThenMarkCallModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyOwnMember");
            Assert.IsTrue((method.Purity & Purity.CallModifySelf) == Purity.CallModifySelf);
        }

        [TestMethod]
        public void WhenCallModifyingBaseMethodThenMarkCallModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyOwnBaseMember");
            Assert.IsTrue((method.Purity & Purity.CallModifySelf) == Purity.CallModifySelf);
        }

        [TestMethod]
        public void WhenCallModifyingMethodIndirectThenMarkCallModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyOwnMemberIndirect");
            Assert.IsTrue((method.Purity & Purity.CallModifySelf) == Purity.CallModifySelf);
        }

        [TestMethod]
        public void WhenCallModifyCommonMethodThenMarkCallModifyCommon()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyStaticField");
            Assert.IsTrue((method.Purity & Purity.CallModifyCommon) == Purity.CallModifyCommon);
        }

        [TestMethod]
        public void WhenCallModifyCommonMethodIndirectThenMarkCallModifyCommon()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyStaticFieldIndirect");
            Assert.IsTrue((method.Purity & Purity.CallModifyCommon) == Purity.CallModifyCommon);
        }

        [TestMethod]
        public void MethodsOfStackAreMarkedModifySelf()
        {
            var methodInfo = typeof(Stack<int>).GetMethod("Push");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfStackAreNotMarked()
        {
            var methodInfo = typeof(Stack<int>).GetMethod("Peek");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfQueueAreMarkedModifySelf()
        {
            var methodInfo = typeof(Queue<int>).GetMethod("Enqueue");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfQueueAreNotMarked()
        {
            var methodInfo = typeof(Queue<int>).GetMethod("Peek");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfICollectionAreMarkedModifySelf()
        {
            var methodInfo = typeof(ICollection<int>).GetMethod("Add");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfICollectionAreNotMarked()
        {
            var methodInfo = typeof(ICollection<int>).GetMethod("Contains");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfIListAreMarkedModifySelf()
        {
            var methodInfo = typeof(IList<int>).GetMethod("Insert");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfIListAreNotMarked()
        {
            var methodInfo = typeof(IList<int>).GetMethod("IndexOf");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfListAreMarkedModifySelf()
        {
            var methodInfo = typeof(List<int>).GetMethod("Insert");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfListAreNotMarked()
        {
            var methodInfo = typeof(List<int>).GetMethod("Contains");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfArrayListAreMarkedModifySelf()
        {
            var methodInfo = typeof(ArrayList).GetMethod("Add");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfArrayListAreNotMarked()
        {
            var methodInfo = typeof(ArrayList).GetMethod("Clone");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfSortedListAreMarkedModifySelf()
        {
            var methodInfo = typeof(SortedList).GetMethod("Add");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfSortedListAreNotMarked()
        {
            var methodInfo = typeof(SortedList).GetMethod("Clone");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfSortedSetAreMarkedModifySelf()
        {
            var methodInfo = typeof(SortedSet<int>).GetMethod("Add");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfSortedSetAreNotMarked()
        {
            var methodInfo = typeof(SortedSet<int>).GetMethod("get_Count");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfSortedDictionaryAreMarkedModifySelf()
        {
            var methodInfo = typeof(SortedDictionary<int, long>).GetMethod("Add");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfSortedDictionaryAreNotMarked()
        {
            var methodInfo = typeof(SortedDictionary<int, long>).GetMethod("get_Count");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfSortedListGenericAreMarkedModifySelf()
        {
            var methodInfo = typeof(SortedList<int, long>).GetMethod("Add");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfSortedListGenericAreNotMarked()
        {
            var methodInfo = typeof(SortedList<int, long>).GetMethod("get_Count");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void MethodsOfLinkedListAreMarkedModifySelf()
        {
            var methodInfo = typeof(LinkedList<int>).GetMethod("Clear");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void PureMethodsOfLinkedListAreNotMarked()
        {
            var methodInfo = typeof(LinkedList<int>).GetMethod("Find");
            var method = DescriptorBuilder.BuildMemberDescriptor(methodInfo);
            Assert.AreEqual(Purity.Undefined, method.Purity);
        }

        [TestMethod]
        public void SettersOfProjectCodeAreMarkedModifySelf()
        {
            var member = _purity2.Members.First(x => x.Name == "set_Property");
            Assert.AreEqual(Purity.ModifySelf, member.Purity);
        }

        [TestMethod]
        public void GettersArePure()
        {
            var member = _purity2.Members.First(x => x.Name == "get_Property");
            Assert.AreEqual(Purity.Pure, member.Purity);
        }

        [Ignore]
        [TestMethod]
        public void SettersOfFrameworkAndForeignCodeAreMarkedModifySelf()
        {

        }

        [TestMethod]
        public void SetIndexerOfProjectCodeAreMarkedModifySelf()
        {
            var member = _purity2.Members.First(x => x.Name == "set_Item");
            Assert.AreEqual(Purity.ModifySelf, member.Purity);
        }

        [TestMethod]
        public void StaticSettersOfProjectCodeAreMarkedModifyCommon()
        {
            var member = _purity2.Members.First(x => x.Name == "set_StaticProperty");
            Assert.AreEqual(Purity.ModifyCommon, member.Purity);
        }

        [TestMethod]
        public void SetStaticArrayItemOfProjectCodeAreMarkedModifyCommon()
        {
            var member = _purity2.Members.First(x => x.Name == "SetItemToStaticArray");
            Assert.AreEqual(Purity.ModifyCommon, member.Purity);
        }

        [TestMethod]
        public void StaticAddEventOfProjectCodeAreMarkedModifyCommon()
        {
            var member = _purity2.Members.First(x => x.Name == "add_StaticEvent");
            Assert.AreEqual(Purity.ModifyCommon, member.Purity);
        }

        [TestMethod]
        public void AddEventOfProjectCodeAreMarkedModifyCommon()
        {
            var member = _purity2.Members.First(x => x.Name == "remove_InstanceEvent");
            Assert.AreEqual(Purity.ModifySelf, member.Purity);
        }

        [TestMethod]
        public void StaticFactoryMethodIsPure()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StaticFactoryMethod");
            Assert.AreEqual(Purity.Pure, method.Purity);
        }

        [TestMethod]
        public void InstanceFactoryMethodIsPure()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "InstanceFactoryMethod");
            Assert.AreEqual(Purity.Pure, method.Purity);
        }

        [TestMethod]
        public void InstanceBuilderMethodIsMarkedCallModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "InstanceBuilderMethod");
            var instructions = Model.InstructionCache.GetInstructions(method);
            Console.WriteLine(instructions);
            Assert.IsTrue((method.Purity & Purity.CallModifySelf) == Purity.CallModifySelf);
        }

        [TestMethod]
        public void ModifyFieldOnLocalIsMarkedPure()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "ModifyFieldOnLocal");
            Assert.AreEqual(Purity.Pure, method.Purity);
        }

        [TestMethod]
        public void CopyToIsModifyParameter()
        {
            var method = _purity2.Members.FirstOrDefault(x => x.Name == "CopyTo");
            Assert.IsTrue((method.Purity & Purity.ModifyParameter) == Purity.ModifyParameter);
        }

        [TestMethod]
        public void CopyFromIsModifySelf()
        {
            var method = _purity2.Members.FirstOrDefault(x => x.Name == "CopyFrom");
            Assert.IsTrue((method.Purity & Purity.ModifySelf) == Purity.ModifySelf);
        }

        [TestMethod]
        public void CopyToIsNotModifySelf()
        {
            var method = _purity2.Members.FirstOrDefault(x => x.Name == "CopyTo");
            Assert.IsTrue((method.Purity & (Purity.ModifySelf | Purity.CallModifySelf)) == 0);
        }

        [TestMethod]
        public void CopyFromIsNotModifyParameter()
        {
            var method = _purity2.Members.FirstOrDefault(x => x.Name == "CopyFrom");
            Assert.IsTrue((method.Purity & (Purity.ModifyParameter | Purity.CallModifyParameter)) == 0);
        }

        [TestMethod]
        public void StoreElementToInstanceArrayFieldIsMarkedModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToInstanceArrayField");
            Assert.IsTrue((method.Purity & Purity.ModifySelf) == Purity.ModifySelf);
        }

        [TestMethod]
        public void StoreElementToInstanceArrayPropertyIsMarkedModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToInstanceArrayProperty");
            Assert.IsTrue((method.Purity & Purity.ModifySelf) == Purity.ModifySelf);
        }

        [TestMethod]
        public void StoreElementToStaticArrayFieldIsMarkedModifyCommon()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToStaticArrayField");
            Assert.IsTrue((method.Purity & Purity.ModifyCommon) == Purity.ModifyCommon);
        }

        [TestMethod]
        public void StoreElementToArgumentArrayIsMarkedModifyParameter()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToArgumentArray");
            var parameters = (from x in Model.DependenciesPerMemberOut[method]
                              where x.ReferenceType == ReferenceType.Parameter
                              select x).ToArray();
            Assert.AreEqual(Purity.ModifyParameter, method.Purity);
            Console.WriteLine(parameters);
        }

        [TestMethod]
        public void StoreElementToInstanceArrayPropertyIsNotMarkedModifyParameter()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToInstanceArray2");
            Assert.IsFalse((method.Purity & Purity.ModifyParameter) == Purity.ModifyParameter);
        }

        [TestMethod]
        public void StoreElementToCreatedArrayIsMarkedPure()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToCreatedArray");
            Assert.AreEqual(Purity.Pure, method.Purity);
        }

        [TestMethod]
        public void StoreElementToReturnedArrayIsMarkedPure()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "StoreElementToReturnedArray");
            Assert.AreEqual(Purity.Pure, method.Purity);
        }

        [TestMethod]
        public void WhenCallingModifySelfMethodOnArgumentThenMarkModifyParameter()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyingMethodOnArgument");
            Assert.AreEqual(Purity.ModifyParameter, method.Purity);
        }

        [TestMethod]
        public void WhenNotCallingMethodOnArgumentThenDontMarkModifyParameter()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "DontCallModifyingMethodOnArgument");
            Assert.IsTrue((method.Purity & Purity.ModifyParameter) != Purity.ModifyParameter);
        }

        [TestMethod]
        public void WhenMethodHasByrefParameterThenMarkModifyParameter()
        {
            var method = _purity3.Members.FirstOrDefault(x => x.Name == "MethodWithRef");
            Assert.IsTrue((method.Purity & Purity.ModifyParameter) == Purity.ModifyParameter);
        }

        [TestMethod]
        public void CallModifySelfMethodOnMemberThenMarkCallModifySelf()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyingMethodOnMember");
            Assert.IsTrue((method.Purity & Purity.CallModifySelf) == Purity.CallModifySelf);
        }

        [TestMethod]
        public void MethodSettingPropertyIsMarkedSelf()
        {
            var method = _purity3.Members.FirstOrDefault(x => x.Name == "ModifyingMethod");
            Assert.AreEqual(Purity.ModifySelf, method.Purity);
        }

        [TestMethod]
        public void MethodCallsModifySelfOnStaticMemberThenMarkCallModifyCommon()
        {
            var method = _purity1.Members.FirstOrDefault(x => x.Name == "CallModifyingMethodOnStaticMember");
            Assert.IsTrue((method.Purity & Purity.CallModifyCommon) == Purity.CallModifyCommon);
        }

        [TestMethod]
        public void MethodCallingPureFunctionsIsMarkedPure()
        {
            var method = _purity3.Members.FirstOrDefault(x => x.Name == "CallFunctional");
            var instructions = Model.InstructionCache.GetInstructions(method);
            Console.WriteLine(instructions);
            Assert.AreEqual(Purity.Pure, method.Purity);
        }
    }
}
